//********************************************************************************************************
// Product Name: MapWindow.dll Alpha
// Description:  Low level interfaces that allow separate components to use objects that are defined
//               in completely independant libraries so long as they satisfy the basic interface requirements.
//********************************************************************************************************
// The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); 
// you may not use this file except in compliance with the License. You may obtain a copy of the License at 
// http://www.mozilla.org/MPL/ 
//
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF 
// ANY KIND, either express or implied. See the License for the specificlanguage governing rights and 
// limitations under the License. 
//
// The Original Code is MapWindow.dll
//
// The Initial Developer of this Original Code is Ted Dunsford. Created in October, 2007.
// 
// Contributor(s): (Open source contributors should list themselves and their modifications here). 
//
//********************************************************************************************************
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using MapWindow.Geometries;
using MapWindow.Main.EventArguments;
namespace MapWindow.Drawing
{
    /// <summary>
    /// Since you need less and less resolution as you zoom out, this stores 4
    /// pre-drawn edge components in each direction.  Zooming out litterally
    /// then justs pastes the pre-calculated image parts into place, shrinking
    /// the parts towards the middle as necessary.
    /// </summary>
    public class ZoomBuffer: object, IView
    {
        #region Variables


        protected Bitmap[] _Images;
        protected int _CurrentImage;
        protected IEnvelope _ViewExtents;
        protected bool _CancelDrawing;
        protected bool _IsBusy;
        protected bool _Visible;
        protected double _ZoomFactor;
        protected Size _ClientSize;
        

        #endregion

        #region Constructors

        /// <summary>
        /// This constructs a blank set of bitmaps
        /// </summary>
        /// <param name="ClientWindow">The size of the target to draw on</param>
        /// <param name="ZoomRatio">When you zoom out, what is the ratio of new 
        /// geographic extents to the old geographic extents?</param>
        public ZoomBuffer(Size clientWindow, double ZoomFactor)
        {
            // By knowing the ZoomFactor, we can figure out the descrete jumps of 
            // zoom that we should draw images for.
            _Images = new Bitmap[40];

               
            _ClientSize = clientWindow;

        }

        #endregion

        #region Methods

        /// <summary>
        /// Completely removes all the images from the buffer
        /// </summary>
        public void Clear()
        {
            _Images = new Bitmap[40];
        }

        public void Draw(Graphics g)
        {
            Draw(g, Rectangle.Empty, Rectangle.Empty);
        }

        public void Draw(Graphics g, Rectangle clipRectangle)
        {
            Draw(g, clipRectangle, Rectangle.Empty);
        }

        public void Draw(Graphics g, Rectangle clipRectangle, Rectangle target)
        {
            try
            {
                _IsBusy = true;
                if (_CancelDrawing == true)
                {
                    OnDrawCanceled(new Exception("CancelDrawing was true"), g, clipRectangle, target);
                }
                if (g == null)
                {
                    OnDrawCanceled(new ArgumentNullException("g was null."), g, clipRectangle, target);
                    return;
                }
                if (OnBeforeDrawing(g, clipRectangle, target) == true)
                {
                    // Canceled... but not because of an error
                    OnDrawCompleted(g, clipRectangle, target);
                    return;
                }
                if (_Images == null)
                {
                    OnDrawCompleted(g, clipRectangle, target);
                    return;
                }
                
                
                // Intersect clip preserves any previous clipping and adds the specified clip
                if (clipRectangle.IsEmpty == false)
                {
                    g.IntersectClip(clipRectangle);
                }
                if (target.IsEmpty == true)
                {
                    g.DrawImage(_Images[_CurrentImage], new System.Drawing.Point(0, 0));
                }
                else
                {
                    g.DrawImage(_Images[_CurrentImage], target);
                }
            }
            catch (Exception ex)
            {
                OnDrawCanceled(ex, g, clipRectangle, target);
                return;
            }
        }

        public void Pan(int dX, int dY)
        {
            // This just needs to translate the view extents technically.
            _ViewExtents.Translate(dX, dY);
        }

        #region PixelToProj

        public void PixelToProj(int pixel_X, int pixel_Y, out double X, out double Y)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public ICoordinate PixelToProj(System.Drawing.Point PixelPosition)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public IEnvelope PixelToProj(Rectangle Window)
        {
            throw new Exception("The method or operation is not implemented.");
        }

       

        #endregion

        #region ProjToPixel

        public void ProjToPixel(double X, double Y, out int pixel_X, out int pixel_Y)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public System.Drawing.Point ProjToPixel(ICoordinate coord)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public Rectangle ProjToPixel(IEnvelope env)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        #endregion

        /// <summary>
        /// This rapid-zoom method is where the appropriate zoom image is selected
        /// and where the ViewExtents are set.  Changing this will not cause
        /// any re-drawing to occur.  
        /// </summary>
        /// <param name="Percent">The ratio of the new extents as compared with the current extents</param>
        public void Zoom(double Percent)
        {
            // This determines the offset in the image array from the current image.
            int Offset = 0;
            double test = 100;
            if (Percent > 100)
            {
                if (_ZoomFactor > 1)
                {
                    while (test < Percent)
                    {
                        test = test * _ZoomFactor;
                        Offset -= 1;
                    }
                }
                else
                {
                    while (test < Percent)
                    {
                        test = test / _ZoomFactor;
                        Offset += 1;
                    }
                }
            }
            if (Percent < 100)
            {
                if (_ZoomFactor > 1)
                {
                    while (test > Percent)
                    {
                        test = test / _ZoomFactor;
                        Offset += 1;
                    }
                }
                else
                {
                    while (Percent < test)
                    {
                        test = test * _ZoomFactor;
                        Offset -= 1;
                    }
                }
            }
            _CurrentImage -= Offset;

            // Stop them before they exceed the buffer's image supply
            // They can zoom more, but only after we built some more buffer images.
            //if (_CurrentImage < 0)
            //{
            //    System.Diagnostics.Debug.WriteLine("Scrolled too low");
            //    _CurrentImage = 0;
            //}
            //if (_CurrentImage > _Images.GetUpperBound(0))
            //{
            //    System.Diagnostics.Debug.WriteLine("Scrolled too high");
            //    _CurrentImage = _Images.GetUpperBound(0);
            //}
            
        }


        #endregion

        #region Properties

        public bool CancelDrawing
        {
            get
            {
                return _CancelDrawing;
            }
            set
            {
                _CancelDrawing = value;
            }
        }

        public Size ClientSize
        {
            get { return _ClientSize; }
            set { _ClientSize = value; }
        }

        /// <summary>
        /// Gets or sets an integer value representing the current image in the
        /// range of images with different zoom levels.
        /// </summary>
        public int CurrentImage
        {
            get { return _CurrentImage; }
            set { _CurrentImage = value; }
        }

        public Graphics Graphics
        {
            get { return Graphics.FromImage(_Images[_CurrentImage]); }
        }

        public Bitmap[] Images
        {
            get { return _Images; }
            set { _Images = value; }
        }

        /// <summary>
        /// If the current image that would normally be drawn is null, then this returns true.
        /// </summary>
        public bool IsEmpty
        {
            get
            {
                if (_CurrentImage > 39 || _CurrentImage < 0) return true;
                if (_Images[_CurrentImage] == null) return true;
                return false;
            }
        }

        ///// <summary>
        ///// Normally, this just returns the image from the current zoom level.
        ///// Sometimes, the zoom level is beyond what is cached, or beyond what
        ///// is stored in the array.  This returns the nearest image.
        ///// </summary>
        //public Bitmap StretchedImage
        //{
        //    get
        //    {
        //        int I = _CurrentImage;
        //        double ExtraZoom = 1;
        //        if (I > 20)
        //        {
        //            while (_Images[I] == null)
        //            {
        //                I--;
        //                ExtraZoom = ExtraZoom * _ZoomFactor;
        //            }
        //        }
        //        else
        //        {
        //            while (_Images[I] == null)
        //            {
        //                I++;
        //                ExtraZoom = ExtraZoom / _ZoomFactor;
        //            }
        //        }
        //        Bitmap temp = new Bitmap(_ClientSize.Width, _ClientSize.Height);
        //        return _CurrentImage[I];
        //    }
        //}

        public bool IsBusy
        {
            get { return _IsBusy; }
        }

        public IEnvelope ViewExtents
        {
            get { return _ViewExtents; }
        }

        public bool Visible
        {
            get
            {
                return _Visible;
            }
            set
            {
                _Visible = value;
            }
        }

        /// <summary>
        /// Gets or sets a double that modifies how quickly the zoom extents change on
        /// a zoom in command.  Larger values will change the extents more, while smaller
        /// values will cause less change in the extents.
        /// </summary>
        public double ZoomFactor
        {
            get { return _ZoomFactor; }
            set
            {
                _ZoomFactor = value;
            }
        }

        #endregion
 

        #region Events


        /// <summary>
        /// Occurs immediately before the paint event, allowing custom drawing before the item draws.
        /// It also provides a variable to allow the event to be canceled.
        /// </summary>
        public virtual event EventHandler<CancelPaint> BeforeDrawing;

        protected bool OnBeforeDrawing(Graphics g, Rectangle clipRect, Rectangle Target)
        {
            if (BeforeDrawing != null)
            {
                CancelPaint e = new CancelPaint(g, clipRect, Target);
                BeforeDrawing(this, e);
                return e.Cancel;
            }
            return false;
        }

        /// <summary>
        /// Occurs immediately after the paint event, giving access to the drawing object for custom
        /// drawing events.  This does not, however, support the ability to cancel an event that has
        /// already occurred.
        /// </summary>
        public virtual event EventHandler<DrawCompleted> DrawCompleted;

        /// <summary>
        /// The drawing method finished, and exited normally.
        /// </summary>
        /// <param name="g">The System.Drawing.Graphics surface used by the draw method.</param>
        /// <param name="clipRect">The System.Drawing.Rectangle defining the area to actually perform drawing</param>
        /// <param name="target">The destination rectangle for the System.Drawing.Rectangle where the result should be drawn</param>
        protected void OnDrawCompleted(Graphics g, Rectangle clipRect, Rectangle Target)
        {
            
            if (DrawCompleted != null)
            {
                DrawCompleted(this, new DrawCompleted(g, clipRect, Target));
            }
            _IsBusy = false;
        }

        /// <summary>
        /// Internal Method to fire the Cancel Event.  This caries the same basic information that the 
        /// DrawingComplete method, plus an exception if any as well as a boolean indicating the draw method was cancelled.
        /// </summary>
        /// <param name="Error">A System.Exception showing the error that caused the cancelation</param>
        /// <param name="g">A System.Drawing.Graphics surface that drawing was taking place on</param>
        /// <param name="clipRect">The ClipRectangle or Rectangle.Empty</param>
        /// <param name="target">The Destination Rectangle or Rectangle.Empty</param>
        protected virtual void OnDrawCanceled(System.Exception Error, System.Drawing.Graphics g, System.Drawing.Rectangle clipRect, System.Drawing.Rectangle target)
        {
            if (DrawCompleted != null)
            {
                DrawCompleted(this, new DrawCompleted(Error, true, g, clipRect, target));
            }
            _IsBusy = false;
        }

        /// <summary>
        /// Occurs after something changes the geographic extents.  This refers to the outer
        /// geographic extents, not the view extents.
        /// </summary>
        public event EventHandler ExtentsChanged;

        protected void OnExtentsChanged()
        {
            if (ExtentsChanged != null)
            {
                ExtentsChanged(this, new EventArgs());
            }
        }


        #endregion


        
    }
}
